#include "gtest/gtest.h"
#define private public
#define protected public
#include "MySmartPtr.h"

using namespace std;

class SmartPtrTest: public testing::Test {
protected:
	virtual void SetUp() {
	}

	virtual void TearDown() {
	}

	class Base {
	public:
		Base() : value_(0) { }
		virtual ~Base() {};
		virtual int getValue() const { return value_; }
		virtual void setValue(int value) { value_ = value; }

		virtual string getName() { return "Base"; }
	private:
		int value_;
	};

	class Derived : public Base {
	public:
		virtual string getName() { return "Derived"; }
	};

	class AnotherBase {
	public:
		virtual ~AnotherBase() {}
		virtual string getName() { return "AnotherBase"; }
	};

	class MultiInherit : public Base, public AnotherBase {
	public:
		virtual string getName() { return "MultiInherit"; }
	};

	MY_SMART_PTR( Base );
	MY_SMART_PTR( Derived );
	MY_SMART_PTR( AnotherBase );
	MY_SMART_PTR( MultiInherit );

	class BlockedSmartPointerClass {
	public:
		BlockedSmartPointerClass() {}
		virtual ~BlockedSmartPointerClass() {}
	};

//	DISABLE_MY_SMART_PTR( BlockedSmartPointerClass );


	class ForwardDeclarationClass;
	MY_SMART_PTR(ForwardDeclarationClass);

	class DependentClassForTheForwardDeclaredClass {
	public:
		virtual ~DependentClassForTheForwardDeclaredClass() {}

		virtual ForwardDeclarationClassPtr getChild() = 0;
	};

	class ForwardDeclarationClass {
	public:
		virtual ~ForwardDeclarationClass() {}
		virtual int getValue() { return 1; }
		ForwardDeclarationClassPtr child;
	};
};

TEST_F(SmartPtrTest, NullTest) {
	DerivedPtr ptr1 = new Derived();
	ASSERT_TRUE( NULL != ptr1 );
	ASSERT_FALSE( !ptr1 );
	ASSERT_TRUE( ptr1 );

	DerivedPtr ptr2 = NULL;
	ASSERT_TRUE( NULL == ptr2 );
	ASSERT_FALSE( ptr2 );
	ASSERT_TRUE( !ptr2 );

	DerivedPtr ptr3(NULL);
	ASSERT_TRUE( NULL == ptr3 );
	ASSERT_FALSE( ptr3 );
	ASSERT_TRUE( !ptr3 );

	DerivedPtr ptr4 = ptr3;
	ASSERT_TRUE( NULL == ptr4 );
	ASSERT_FALSE( ptr4 );
	ASSERT_TRUE( !ptr4 );
}

TEST_F(SmartPtrTest, Inheritance) {
	BasePtr ptr1 = new Base();
	ASSERT_STREQ("Base", ptr1->getName().c_str());

	BasePtr ptr2 = new Derived();
	ASSERT_STREQ("Derived", ptr2->getName().c_str());
}

TEST_F(SmartPtrTest, Const) {
	const BasePtr ptr1 = new Base();
	ptr1->setValue(3);
	ASSERT_EQ(3, ptr1->getValue());

	const BaseConstPtr ptr2 = new Base();
	// uncomment when you want to test it.
//	ptr2->setValue(3);
	ASSERT_NE(3, ptr2->getValue());
}

TEST_F(SmartPtrTest, ConstConvertion) {
	const BasePtr ptr1 = new Base();
	const BaseConstPtr ptr2 = ptr1;
	ASSERT_TRUE(ptr1 == ptr2);

	const BaseConstPtr ptr3 = new Base();
	// uncomment when you want to test this.
//	const BasePtr ptr4 = ptr3;
//	ASSERT_EQ(ptr3, ptr4);
}

TEST_F(SmartPtrTest, MultiInheritance) {
	MultiInheritPtr ptr1 = new MultiInherit();
	ASSERT_EQ((unsigned int) 1, *(ptr1.pCount_) );
	{
		BasePtr ptr2 = ptr1;
		AnotherBasePtr ptr3 = ptr1;
		ASSERT_EQ((unsigned int) 3, *(ptr1.pCount_) );
	}
	ASSERT_EQ((unsigned int) 1, *(ptr1.pCount_) );
}

TEST_F(SmartPtrTest, StreamOut) {
	const BasePtr ptr1 = new Base();
	ostringstream out1;
	out1 << ptr1;
	ASSERT_TRUE( out1.str().size() > 0 ) << "Address number is not returned.";

	const BasePtr ptr2(NULL);
	ostringstream out2;
	out2 << ptr2;
	ASSERT_STREQ( "0", out2.str().c_str() ) << "Null pointer is not recognized.";
}

TEST_F(SmartPtrTest, PointerCoutingWithBaldPointer) {
	const BasePtr ptr1 = new Base();
	ASSERT_EQ((unsigned int) 1, *(ptr1.pCount_) );

	Base * const ptr2 = (Base*) ptr1.GetPointer(ptr1);
	ASSERT_NE( (void*) NULL, ptr2 );
	ASSERT_EQ((unsigned int) 1, *(ptr1.pCount_) );

//	const BasePtr ptr3 = ptr2;
//	ASSERT_EQ((unsigned int) 2, *(ptr3.pCount_) ); // DO NOT BY PASS BARE POINTER.
}

TEST_F(SmartPtrTest, TypeConversionByMistype) {
	const BasePtr ptr1 = new Base();
//	Base obj = ptr1;
}

TEST_F(SmartPtrTest, BlockedSmartPointer) {
//	BlockedSmartPointerClassPtr blocked1 = new BlockedSmartPointerClass();
//	BlockedSmartPointerClassConstPtr blocked2 = new BlockedSmartPointerClass();
	BlockedSmartPointerClass * onlyBarePointerWorks = new BlockedSmartPointerClass();
	ASSERT_NE( (void*) NULL, onlyBarePointerWorks );
}

TEST_F(SmartPtrTest, ForwardDeclaration) {
	ForwardDeclarationClassPtr parent = new ForwardDeclarationClass();
	ASSERT_EQ( 1, parent->getValue() );
}

TEST_F(SmartPtrTest, DowncastPolicy) {
	BasePtr base = new Derived();
//	DerivedPtr derived = base;
//	BlockedSmartPointerClass = base;
}

TEST_F(SmartPtrTest, InheritedExceptionPointers) {
	try {
		throw new Derived();
	}
    catch (Base * const& ) {
    	// expected
    }
    catch(...) {
        FAIL() << "No catch.";
    }

    //-------------------------------
    try {
//        throw DerivedPtr(new Derived()); // THIS DO NOT WORK !!!!
        throw BasePtr(new Derived()); // only this way works.
    }
    catch (BasePtr const&) {
    	// expected
    }
    catch(...) {
        FAIL() << "No catch.";
    }

    //-----------------------------
    try {
//    	throw new Base(); // THIS DO NOT WORK !!!
    }
    catch (BasePtr const& ) {
    	// expected
    }
    catch(...) {
        FAIL() << "No catch.";
    }
}


